接下來的幾天會開始介紹 trpc backend 跟 client端的使用:
backend 部分:
client 部分:
額外部分:
本次 trpc 章節會以上面的 list 為主,筆者會盡量補齊 trpc 的全家桶給大家學習,如果讀者有什麼想了解的部分可以下方討論喔~本次的 trpc 會全部以 Nextjs 這個 framework 為主,trpc 他有提供大部分 nodejs 相關生態系給大家使用:
backend:
client:
大家可以根據喜愛的框架去做選擇,會這次會用 Nextjs 原因是 trpc 的主要生態系會是以 Nextjs 為主,所以為了帶給讀者更完整齊全的教學,再加上本人比較習慣 Nextjs 所以會選用他,然後文章大部分的範例都是從官網來的,筆者會以官網的 demo 為主慢慢做延伸。
開始前先起一個空白 next 專案
npx create-next-app@latest next_demo
$ npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
首先在先 src 中新增 api/root.ts 與 next page。
src
├── pages
│ └── _app.tsx
└── server
└── api
└── trpc.ts
// _app.tsx
import { api } from '@/utils/api'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp;
所有 route 需需要透過 initTRPC.create() 去初始化,所有的 api handler 都是透過 t 這個 instance 創建出來的。
// ~/server/api/trpc.ts
import { initTRPC } from '@trpc/server';
// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();
export const router = t.router;
export const middleware = t.middleware;
export const publicProcedure = t.procedure;
新增 root.ts 用於管理所有的 route
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
└── api
├── root.ts
└── trpc.ts
以下的範例就是創建一個 greeting api,query 就是你 api 的結果,這邊要注意個是記得加 AppRouter 這個 type 因為之後 client端會去使用到喔~
// ~/server/api/root.ts
import * as trpc from '@trpc/server';
import { publicProcedure, router } from './trpc';
const appRouter = router({
greeting: publicProcedure.query(() => 'hello tRPC v10!'),
});
// Export only the type of a router!
// This prevents us from importing server code on the client.
export type AppRouter = typeof appRouter;
新增 ~/app/api/trpc/[trpc]/route.ts file
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
└── api
├── root.ts
└── trpc.ts
因為 nextjs 有 app folder 跟 page folder 使用差異,但這邊筆者建議使用 page folder 為主, trpc 目前對於 server compnent 的完整度還不齊全,但還是簡單 demo 一下 寫法差異,這邊要注意一下不能兩者混用,這樣 next 會不知道他 api 要吃 page 的還是 app 的。
// ~/pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '@/server/api/root';
export default createNextApiHandler({
router: appRouter,
createContext: () => ({}),
});
// ~/app/api/trpc/[trpc]/route.ts
import { appRouter } from '@/server/api/root';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({})
});
export { handler as GET, handler as POST };
最後查看結果,恭喜你!!你成功做到人生第一隻用 trpc 做的 api 了!!有沒有找到人生第一次寫 hello world 的感動呢XD
最後需要添加 api instance 給 client 端去接,接著新增 utils/api.ts 檔案
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
│ └── api
│ ├── root.ts
│ └── trpc.ts
└── utils
└── api.ts
下面一一介紹 createTRPCNext 方法:
// utils/api.ts
import { AppRouter } from '@/server/api/root';
import { httpBatchLink, loggerLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
export const api = createTRPCNext<AppRouter>({
config(opts) {
return {
links: [
httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @link https://trpc.io/docs/ssr
**/
url: `http://localhost:3000`,
// You can pass any HTTP headers you wish here
async headers() {
return {
};
},
}),
],
};
},
/**
* @link https://trpc.io/docs/ssr
**/
ssr: false,
});
httpBatchLink : trpc 是透過 https request 去請求連結到 trpc 的 procedure ,然後處去發 api route 邏輯,如上面定義的 route。headers : 這邊跟就是放你 req header 的地方,例如常見的 Authorization 你可以這樣寫。
httpBatchLink({
url: `http://localhost:3000`,
async headers() {
return {
Authorization: `Bearer ${token}`
};
}
}),
ssr: 因為 trpc 預設會在 server 跑 getInitialProps 做 prefetch,這樣會造成 api response 時間太長,解法有兩個在 header 加 cache-control,或是把 ssr: false,然後透過 ssr helper 手動添加特定頁面做 prefetch,ssr helper 日後會跟大家介紹,這邊記得先加 ssr:false。
詳細資料可以看這邊
最後記得在 _app.tsx 包 api.withTRPC 這樣 trpc 才能在 client 端呼叫喔~
//_app.tsx
import { api } from '@/utils/api'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default api.withTRPC(MyApp);
添加 index.tsx
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
│ └── api
│ ├── root.ts
│ └── trpc.ts
└── utils
└── api.ts
讀者看到這邊先恭喜你已經完成 client 端連結拉,那因為 client 端是封裝 react query ,所以使用起來跟 react query 一樣呦~
//
import { api } from "@/utils/api";
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import Link from "next/link";
export default function Home() {
const { data, isLoading, isError } = api.greeting.useQuery()
if (isLoading) return 'isLoading'
if (isError) return 'isError'
return (
<>
{data}
</>
);
}
trpc 可以透過 responseMeta 來設定 cache-control 的值,這邊簡單介紹下面 demo 用到的內容。
代表 1 天內的 response 都是返回 cache 資料,一天後重新發 request
代表 1s 內的 response 都是返回 cache 資料 , 1s 到 1 天內在背景更改 cache data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
responseMeta(opts) {
const { ctx, paths, errors, type } = opts
const allPublic = paths && paths.every(path => path.includes('public'))
const allOk = errors.length === 0
const isQuery = type === 'query'
if (
allPublic &&
allOk &&
isQuery
) {
const ONE_DAY_IN_SECONDS = 60 * 60 * 24
// max-age=60 * 60 * 24 代表 1 天內 內的 response 都是返回 cache 資料,一天後重新發 request
// max-age=1, stale-while-revalidate=60 * 60 * 24 代表 1s 內的 response 都是返回 cache 資料 , 1s - 1 天內 背景更改 cache data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。
return {
headers: {
'cache-control': `max-age=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
}
}
}
return {}
}
});
github : https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y